[Swift] UIApplicationを継承したカスタムなアプリケーションクラスを使用する
はじめに
モバイルアプリサービス部の中安です。
UIApplication
のシングルトンインスタンスに対して処理をお願いする場面は、iOSアプリを開発してる中で出てくると思います。
たとえば
// ホーム画面のアイコンにバッジを付けたい時 UIApplication.shared.applicationIconBadgeNumber = 9
という感じです。
このshared
で受け取れるインスタンスは当然ながらUIApplication
オブジェクトです。
そのインスタンスをカスタムなアプリケーションクラスに差し替えて、処理ごとオーバライドしてやることも可能です。
まぁ、そういうことをする機会こそ少ないとは思うのですが、 実際にはどうやってやればいいのかというのが今回のお題です。
カスタムなアプリケーションクラスを用意
class Application: UIApplication { }
シンプルにUIApplication
を継承してやります。名前はなんでもいいです。ここではApplication
としています。
Application.swift
ファイルを用意して、プロジェクトに追加してやります。
ただし、これだけでは実は何の役にも立ちません。
main.swift
言語をswiftで設定されたiOSアプリのプロジェクトでは、いわゆる「メイン関数」が書かれたファイルはありません(objective-cでは存在した)。
でも実はmain.swift
というファイルをプロジェクトに追加してやることで、
メイン関数の役割を果たすファイルだと認識され、最初に実行されるようになります。
プロジェクトにmain.swift
ファイルを追加して以下のように書いてやります。
import UIKit let argc = CommandLine.argc let argv = CommandLine.unsafeArgv let appDelegate = NSStringFromClass(AppDelegate.self) let application = NSStringFromClass(UIApplication.self) let code = UIApplicationMain(argc, argv, application, appDelegate) exit(code)
ちなみに上記の書き方は swift4.2からの記述で、それ以前についてはargv
への代入の仕方が少し違います。
// swift4.2からはこの書き方はdeprecated let argv = UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory( to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc) )
さて、このmain.swift
の中でアプリケーションクラスを指定してやる箇所があると思うので、
その箇所を変更してあげましょう。
let application = NSStringFromClass(UIApplication.self) ↓ let application = NSStringFromClass(Application.self) // 名前は適宜書き換えてください
これでカスタムなApplication
クラスがUIApplication
から差し替わったはずですが、
このままビルドすると残念ながらコンパイルエラーになると思います。
@UIApplicationMainアトリビュート
前項で出たコンパイルエラーのメッセージは
'UIApplicationMain' attribute cannot be used in a module that contains top-level code
という内容だと思います。
これはどういう意味でしょうか。
その説明は、swiftドキュメントの「アトリビュート」について書かれた箇所にて言及されています。
Apply this attribute to a class to indicate that it’s the application delegate.
アプリケーションデリゲートであることを示すためにこのアトリビュートを適用してください。
Using this attribute is equivalent to calling the UIApplicationMain function and passing this class’s name as the name of the delegate class.
このアトリビュートを使用することは、UIApplicationMain関数を呼び出し、このクラスの名前をデリゲートクラスの名前として渡すことと同じです。
If you don’t use this attribute, supply a main.swift file with code at the top level that calls the UIApplicationMain(_:_:_:_:) function.
このアトリビュートを使用しない場合、トップレベルにおいてUIApplicationMain(_:_:_:_:)関数を呼び出すmain.swiftファイルを配置します。
For example, if your app uses a custom subclass of UIApplication as its principal class, call the UIApplicationMain(_:_:_:_:) function instead of using this attribute.
例えば、アプリにカスタムなUIApplicationのサブクラスを主幹クラスとして使用するならば、このアトリビュートを使用する代わりにUIApplicationMain(_:_:_:_:)関数を呼び出します。
まさに当記事でやろうとしている解説が、@UIApplicationMainアトリビュート
の説明に記載されているのですが、
AppDelegate
にはすでに@UIApplicationMain
がクラスの前に記載されていると思います。
main.swift
を使用してカスタムなアプリケーションクラスを設定してやりたい場合は、このアトリビュートは共存できないとされています。
なので、このアトリビュートを消してやります。
@UIApplicationMain ← ここを削除する class AppDelegate: UIResponder, UIApplicationDelegate { // 中身は省略 }
するとビルドができるようになります。
使ってみる
最初の例にあったバッジ数をオーバライドしてみます。
class Application: UIApplication { override var applicationIconBadgeNumber: Int { get { return super.applicationIconBadgeNumber } set { super.applicationIconBadgeNumber = newValue > 0 ? 1 : 0 } } }
こうすることで、どの場所からバッジ数の変更が呼び出されても
ホーム画面上のバッジは1
か0
しかつかないようになります。
最後に
方法は書き連ねましたが、この記事でしめしたバッジ数の変更方法はテスタブルなものとはいえず、 また不要なバグを仕込んでしまいそうではあるので避けるべきではあると思います。
アプリケーション全体で使う何かしらのインスタンスをここに格納しておくという手法も浮かびそうですが、 それもまた得策とは言えません。
使いどころは設計時に慎重に考える必要はありそうですが、何かの参考になれば幸いです。